﻿namespace NKernel.Core.Impl
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    using System.Xml;

    using log4net;

    /// <summary>
    /// Description of FileComponentLoader.
    /// </summary>
    internal class FileComponentLoader : IComponentLoader
    {
        #region Fields

        private const string ComponentDescriptionXpath = "/Services/Service";
        private const string ComponentFile = "Component.xml";
        private const string ComponentImplElement = "ComponentImpl";
        private const string ComponentInterfaceElement = "ComponentInterface";
        private const string ComponentNameElement = "ComponentName";
        private const string DllRegex = "*.dll";

        private readonly ILog logger = LogManager.GetLogger(typeof(FileComponentLoader));

        #endregion Fields

        #region Methods

        public IList<ComponentDescriptor> LoadComponents(string[] locations, ComponentOverrideDirective directive)
        {
            if (directive == null)
            {
                const string msg = "Component Override directive is null when it should not be the case!";
                logger.Error(msg);
                throw new ArgumentNullException(msg);
            }

            IList<ComponentDescriptor> descriptorList = new List<ComponentDescriptor>();
            foreach (string location in locations)
            {
                var di = new DirectoryInfo(location);
                if (!di.Exists)
                {
                    logger.Warn("Non-existent directory: " + location);
                    continue;
                }
                FileInfo[] fileInfos = di.GetFiles(DllRegex);
                foreach (FileInfo fileInfo in fileInfos)
                {
                    string fullComponentPath = fileInfo.FullName;
                    Assembly assembly = Assembly.LoadFrom(fullComponentPath);
                    string assemblyNameSpace = assembly.GetName().Name;

                    string fullQualifiedResourceName = assemblyNameSpace + "." + ComponentFile;    // it should look like this - "MyNamespace.MyTextFile.txt"
                    if (logger.IsDebugEnabled)
                    {
                        logger.Debug(String.Format("Looking for resource - {0}", fullQualifiedResourceName));
                    }
                    Stream resourceStream = assembly.GetManifestResourceStream(fullQualifiedResourceName);

                    if (resourceStream == null)
                    {
                        logger.Info(String.Format("Component ({0}) does not have the component config file {1}",
                                                  fullComponentPath, ComponentFile));
                    }
                    else
                    {
                        var xmlDoc = new XmlDocument();
                        try
                        {
                            xmlDoc.Load(resourceStream);
                        }
                        catch (Exception exception)
                        {
                            logger.Error(string.Format("Problem loading the XML descriptor from file: {0}",
                                fullComponentPath), exception);
                            continue;
                        }

                        XmlNodeList nodes = xmlDoc.SelectNodes(ComponentDescriptionXpath);

                        if (nodes == null)
                        {
                            logger.Warn("Why no component definition for " + fullQualifiedResourceName);
                            return descriptorList;
                        }

                        foreach (XmlNode node in nodes)
                        {
                            XmlNode nodeName = node[ComponentNameElement];
                            XmlNode nodeInterface = node[ComponentInterfaceElement];
                            XmlNode nodeImpl = node[ComponentImplElement];

                            if (nodeName != null && nodeInterface != null && nodeImpl != null)
                            {
                                if (!String.IsNullOrEmpty(nodeName.InnerText) && !String.IsNullOrEmpty(nodeInterface.InnerText) &&
                                    !String.IsNullOrEmpty(nodeImpl.InnerText))
                                {
                                    var componentName = nodeName.InnerText;
                                    var componentInterface = nodeInterface.InnerText;

                                    var overriddenImpl = directive.ComponentImplementation(componentName);
                                    var componentImpl = String.IsNullOrEmpty(overriddenImpl) ? overriddenImpl : nodeImpl.InnerText;

                                    var descriptor = new ComponentDescriptor(componentName, componentInterface, componentImpl, assemblyNameSpace, assembly)
                                                                         {ComponentPath = fullComponentPath};
                                    if (logger.IsDebugEnabled)
                                    {
                                        logger.Debug("Adding new component descriptor: " + descriptor);
                                    }
                                    descriptorList.Add(descriptor);
                                }
                                else
                                {
                                    logger.WarnFormat(String.Format("Ensure that the component ({0}) is correctly described", assemblyNameSpace));
                                }
                            }
                            else
                            {
                                logger.WarnFormat(String.Format("Ensure that the XML elements in the component config file ({0}) is/are properly configured", ComponentFile));
                            }
                        }

                    }
                }
            }
            return descriptorList;
        }

        #endregion Methods
    }
}